﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="SpriteBatchRenderer.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2011 
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.GUI.
//
//   It is released under the Microsoft Public License (Ms-PL).
//
//   You should have received a copy of the Microsoft Public License
//   along with starLiGHT.GUI. If not, see http://sle.codeplex.com/license.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.GUI are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 9623                  $:  Revision of last commit *
// * $Author:: unknown            $:  Author of last commit   *
// * $Date:: 2011-05-02 22:25:06 #$:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.GUI
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using starLiGHT.GUI.Widgets;
    #endregion

    public class SpriteBatchRenderer : IGuiRenderer
    {
        #region Private Members
#if XNA4
        private static RasterizerState ScissorOnState = new RasterizerState() { ScissorTestEnable = true };
#endif
        private GuiManager guiManager;
        private GraphicsDevice graphics;
        private SpriteBatch spriteBatch;
        private Stack<ScissorStatus> scissorStack;
        private Matrix viewMatrix;
        private bool isScissoringEnabled;

        #endregion

        public SpriteBatchRenderer(GuiManager guiManager, GraphicsDevice graphics)
        {
            if (guiManager == null)
            {
                throw new ArgumentException("guiManager");
            }

            if (graphics == null)
            {
                throw new ArgumentException("graphics");
            }

            this.guiManager = guiManager;
            this.graphics = graphics;

            this.spriteBatch = new SpriteBatch(graphics);

            this.scissorStack = new Stack<ScissorStatus>();
            this.isScissoringEnabled = true;

            this.viewMatrix = Matrix.Identity;
        }

        public GuiManager GuiManager
        {
            get
            {
                return this.guiManager;
            }
        }

        public GraphicsDevice GraphicsDevice
        {
            get
            {
                return this.graphics;
            }
        }

        public SpriteBatch SpriteBatch
        {
            get
            {
                return this.spriteBatch;
            }
        }

        public Matrix ViewMatrix
        {
            get
            {
                return viewMatrix;
            }
            set
            {
                viewMatrix = value;
            }
        }

        /// <summary>
        /// If scissoring is not enabled, the performance increases slightly. 
        /// If you use a view matrix, it should be disabled. 
        /// Then no control should crop anything.
        /// </summary>
        public bool IsScissoringEnabled
        {
            get
            {
                return isScissoringEnabled;
            }
            set
            {
                isScissoringEnabled = value;
            }
        }

        public void Draw(GameTime gameTime)
        {
#if !XNA4
            this.spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
#else
            this.spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, viewMatrix);
#endif

            foreach (Widget w in GuiManager.Widgets)
            {
                w.Draw(gameTime, this);
            }

            // *************************************************
            // draw the mouse pointer on top of everything else
            // *************************************************
            if (GuiManager.SpriteCursorEnabled)
            {
                this.spriteBatch.Draw(GuiManager.SpriteCursor, GuiManager.PointerPosition, Color.White);
            }

            this.spriteBatch.End();
        }

        public void DrawFilledRectangle(RectangleF rect, Color color)
        {
            this.spriteBatch.DrawRectangle(rect, color);
        }

        public void ScissorPush(RectangleF scissor)
        {
            if (!isScissoringEnabled)
                return;

            ScissorStatus actStatus;
                
#if !XNA4
            actStatus = new ScissorStatus(this.GraphicsDevice.RenderState.ScissorTestEnable, this.GraphicsDevice.ScissorRectangle);
#else
            actStatus = new ScissorStatus(this.GraphicsDevice.RasterizerState, this.GraphicsDevice.ScissorRectangle);
#endif

            this.scissorStack.Push(actStatus);

            RectangleF vprect = new RectangleF(GraphicsDevice.Viewport.X, GraphicsDevice.Viewport.Y, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height);
            GraphicsDevice.ScissorRectangle = RectangleF.Clip(scissor, vprect).ToRectangle();

#if !XNA4
            GraphicsDevice.RenderState.ScissorTestEnable = true;
#else
            GraphicsDevice.RasterizerState = ScissorOnState;
#endif
        }

        public void ScissorPop()
        {
            if (!isScissoringEnabled)
                return;

            // The next four lines are very important, including the ordering of this lines
            // the .End() is for flushing the SpriteBatch buffer. If it is missing, clipping is
            // not working properly. Then the ScissorRectangle is resetted and finally a new 
            // SpriteBatch is started.
            SpriteBatch.End();

            ScissorStatus lastStatus = this.scissorStack.Pop();

            GraphicsDevice.ScissorRectangle = lastStatus.ScissorRectangle;

#if !XNA4
            GraphicsDevice.RenderState.ScissorTestEnable = lastStatus.ScissorEnabled;

            SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
#else
            GraphicsDevice.RasterizerState = lastStatus.RasterizerState;
            SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
#endif
        }

        public void DrawSprite(Texture2D texture, float depth, float x, float y, float width, float height, float texX, float texY, float texWidth, float texHeight, Color tint)
        {
            this.SpriteBatch.Draw(texture, new Rectangle((int)x, (int)y, (int)width, (int)height), new Rectangle((int)texX, (int)texY, (int)texWidth, (int)texHeight), tint);
        }

        public void DrawLayer(Widget w, Layer layer)
        {
            float depth = w.ZOrder;

            RectangleF displayRectangle = w.DisplayRectangle; // probably better: layer.OffsetMargin.Apply(w.DisplayRectangle);
            displayRectangle.X += layer.OffsetMargin.Left;
            displayRectangle.Y += layer.OffsetMargin.Top;
            displayRectangle.Width += layer.OffsetMargin.Right;     // correct?
            displayRectangle.Height += layer.OffsetMargin.Bottom;   // correct? maybe the x- / y-offset must be subtracted...

            RectangleF clientRectangle = displayRectangle;
            if (layer.Width > 0)
            {
                clientRectangle.Width = layer.Width;
            }
            else
            {
                clientRectangle.Width += layer.ContentMargin.Right;
            }

            if (layer.Height > 0)
            {
                clientRectangle.Height = layer.Height;
            }
            else
            {
                clientRectangle.Height += layer.ContentMargin.Bottom;
            }

            Vector2 clientPosition = w.GetPosition(displayRectangle, clientRectangle, layer.OffsetMargin, layer.Alignment);

            if (layer.Image != null)
            {
                if (layer.TileMargin.IsEmpty)
                {
                    // plain rectangle
                    this.DrawSprite(layer.Image.Texture, depth, clientPosition.X, clientPosition.Y, clientRectangle.Width, clientRectangle.Height, layer.Image.Rectangle.Left, layer.Image.Rectangle.Top, layer.Image.Rectangle.Width, layer.Image.Rectangle.Height, w.Tint);
                }
                else
                {
                    // tiled rectangle
                    float[] x = new float[] { displayRectangle.Left, displayRectangle.Left + layer.TileMargin.Left, displayRectangle.Right - layer.TileMargin.Right, displayRectangle.Right, };
                    float[] y = new float[] { displayRectangle.Top, displayRectangle.Top + layer.TileMargin.Top, displayRectangle.Bottom - layer.TileMargin.Bottom, displayRectangle.Bottom, };
                    float[] tx = new float[] { layer.Image.Rectangle.Left, (layer.Image.Rectangle.Left + layer.TileMargin.Left), (layer.Image.Rectangle.Right - layer.TileMargin.Right), layer.Image.Rectangle.Right, };
                    float[] ty = new float[] { layer.Image.Rectangle.Top, (layer.Image.Rectangle.Top + layer.TileMargin.Top), (layer.Image.Rectangle.Bottom - layer.TileMargin.Bottom), layer.Image.Rectangle.Bottom, };

                    this.DrawSprite(layer.Image.Texture, depth, x[0], y[0], x[1] - x[0], y[1] - y[0], tx[0], ty[0], tx[1] - tx[0], ty[1] - ty[0], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[1], y[0], x[2] - x[1], y[1] - y[0], tx[1], ty[0], tx[2] - tx[1], ty[1] - ty[0], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[2], y[0], x[3] - x[2], y[1] - y[0], tx[2], ty[0], tx[3] - tx[2], ty[1] - ty[0], w.Tint);

                    this.DrawSprite(layer.Image.Texture, depth, x[0], y[1], x[1] - x[0], y[2] - y[1], tx[0], ty[1], tx[1] - tx[0], ty[2] - ty[1], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[1], y[1], x[2] - x[1], y[2] - y[1], tx[1], ty[1], tx[2] - tx[1], ty[2] - ty[1], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[2], y[1], x[3] - x[2], y[2] - y[1], tx[2], ty[1], tx[3] - tx[2], ty[2] - ty[1], w.Tint);

                    this.DrawSprite(layer.Image.Texture, depth, x[0], y[2], x[1] - x[0], y[3] - y[2], tx[0], ty[2], tx[1] - tx[0], ty[3] - ty[2], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[1], y[2], x[2] - x[1], y[3] - y[2], tx[1], ty[2], tx[2] - tx[1], ty[3] - ty[2], w.Tint);
                    this.DrawSprite(layer.Image.Texture, depth, x[2], y[2], x[3] - x[2], y[3] - y[2], tx[2], ty[2], tx[3] - tx[2], ty[3] - ty[2], w.Tint);
                }
            }

            foreach (TextLabel te in layer.TextLabels)
            {
                w.DrawText(this, layer, te);
            }

            foreach (Layer sublayer in layer.Layers)
            {
                this.DrawLayer(w, sublayer);

                foreach (TextLabel te in sublayer.TextLabels)
                {
                    w.DrawText(this, layer, te);
                }
            }
        }

        public void DrawString(SpriteFont font, string text, Vector2 topLeft, Color color)
        {
            if (font == null)
            {
                throw new ArgumentNullException("font");
            }

            if (string.IsNullOrEmpty("text"))
            {
                throw new ArgumentNullException("text");
            }

            this.spriteBatch.DrawString(font, text, topLeft, color);
        }
    }
}
